/*
 * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Huawei Technologies
 *
 */

#ifndef LWIP_ARCH_PERF_H
#define LWIP_ARCH_PERF_H

#include <time.h>

#include "lwip/debug.h"

#if LWIP_RECORD_PERF
enum PERF_POINT {
    PERF_POINT_IP_RECV,
    PERF_POINT_TCP_RECV,
    PERF_POINT_UDP,
    PERF_POINT_TCP_SYN_RECV,
    PERF_POINT_TCP_SYN_ACK_SEND,
    PERF_POINT_TCP_ACK_RECV,
    PERF_POINT_TCP_SYN_SEND,
    PERF_POINT_TCP_SYN_ACK_RECV,
    PERF_POINT_TCP_ACK_SEND,
    PERF_POINT_TCP_DATA_SEND,
    PERF_POINT_IP_SEND,
    PERF_POINT_END
};

enum PERF_LAYER {
    PERF_LAYER_IP,
    PERF_LAYER_TCP,
    PERF_LAYER_UDP,
    PERF_LAYER_END
};

extern uint32_t g_record_perf;

extern __thread uint64_t g_timeTaken[PERF_POINT_END];
extern __thread int g_perfPoint[PERF_LAYER_END];
extern __thread struct timespec tvStart[PERF_LAYER_END];

extern char *g_ppLayerName[PERF_POINT_END];
extern volatile uint64_t g_perfMaxtime[PERF_POINT_END];
extern volatile uint64_t g_astPacketCnt[PERF_POINT_END];
extern volatile uint64_t g_astPacketProcTime[PERF_POINT_END];

#define PERF_START(layer, point) do {\
    g_perfPoint[(layer)] = (point);\
    LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("set point %d:%s\n", layer, g_ppLayerName[g_perfPoint[(layer)]]));\
    clock_gettime(CLOCK_MONOTONIC, &tvStart[(layer)]);\
    g_timeTaken[(point)] = 0;\
} while (0)

#define PERF_UPDATE_POINT(layer, point) do {\
    LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("old point %d:%s\n", layer, g_ppLayerName[g_perfPoint[(layer)]]));\
    g_timeTaken[(point)] = g_timeTaken[g_perfPoint[(layer)]];\
    g_timeTaken[g_perfPoint[(layer)]] = 0;\
    g_perfPoint[(layer)] = (point);\
    LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("new point %d:%s\n", layer, g_ppLayerName[g_perfPoint[(layer)]]));\
} while (0)

#define PERF_PAUSE(layer) do {\
    struct timespec tvEnd;\
    clock_gettime(CLOCK_MONOTONIC, &tvEnd);\
    LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("perf pause layer%d\n", layer));\
    g_timeTaken[g_perfPoint[(layer)]] += ((tvEnd.tv_sec - tvStart[(layer)].tv_sec) \
            * (1000000000UL) + (tvEnd.tv_nsec - tvStart[(layer)].tv_nsec));\
} while (0)

#define PERF_PAUSE_RETURN_POINT(layer, pause_point) do {\
    struct timespec tvEnd;\
    clock_gettime(CLOCK_MONOTONIC, &tvEnd);\
    g_timeTaken[g_perfPoint[(layer)]] += ((tvEnd.tv_sec - tvStart[(layer)].tv_sec) \
            * (1000000000UL) + (tvEnd.tv_nsec - tvStart[(layer)].tv_nsec));\
    LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("perf pause point %d:%s\n", layer, g_ppLayerName[g_perfPoint[(layer)]]));\
    (pause_point) = g_perfPoint[(layer)];\
} while (0)


#define PERF_RESUME(layer, point) do {\
    LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("perf resule point %d:%s\n", layer, g_ppLayerName[point]));\
    clock_gettime(CLOCK_MONOTONIC, &tvStart[(layer)]);\
    g_perfPoint[(layer)] = (point);\
} while (0)


/* x is a prompt */
#define PERF_STOP_INCREASE_COUNT(x, layer) do {\
    if (g_record_perf)\
    {\
        struct timespec tvEnd;\
        int i = 2;\
        uint32_t oldValue = 0;\
        clock_gettime(CLOCK_MONOTONIC, &tvEnd);\
        g_timeTaken[g_perfPoint[(layer)]] += ((tvEnd.tv_sec - tvStart[(layer)].tv_sec) \
                * (1000000000UL) + (tvEnd.tv_nsec - tvStart[(layer)].tv_nsec));\
        while (i && !oldValue)\
        {\
            oldValue = __sync_or_and_fetch(&g_perfMaxtime[g_perfPoint[(layer)]], 0);\
            if (oldValue >= g_timeTaken[g_perfPoint[(layer)]])\
            {\
                break;\
            }\
            oldValue = __sync_val_compare_and_swap(&g_perfMaxtime[g_perfPoint[(layer)]],\
                    oldValue, g_timeTaken[g_perfPoint[(layer)]]);\
            i--;\
        }\
        __sync_fetch_and_add(&g_astPacketCnt[g_perfPoint[(layer)]], 1);\
        __sync_fetch_and_add(&g_astPacketProcTime[g_perfPoint[(layer)]], g_timeTaken[g_perfPoint[(layer)]]);\
        LWIP_DEBUGF(PERF_OUTPUT_DEBUG, ("Time for %s is: %ld\n",\
                g_ppLayerName[g_perfPoint[(layer)]], g_timeTaken[g_perfPoint[(layer)]]));\
    }\
} while (0)


int check_layer_point(int layer, int point);
int perf_init();

#else
#define PERF_START(layer, point)         do {  } while (0)
#define PERF_UPDATE_POINT(layer, point)  do {  } while (0)
#define PERF_PAUSE(layer)                do {  } while (0)
#define PERF_PAUSE_RETURN_POINT(layer, pause_point) do {  } while (0)
#define PERF_RESUME(layer, point)           do {  } while (0)
#define PERF_STOP_INCREASE_COUNT(x, layer)  do {  } while (0)
#endif

#endif /* LWIP_ARCH_PERF_H */
